在 Kubernetes 中,StatefulSet
是一種專門用來管理有狀態應用的工作負載控制器。與無狀態應用不同,有狀態應用通常需要每個 Pod 有穩定的網絡標識符(如 DNS 名稱)和持久性存儲。StatefulSet 保證了 Pod 的穩定標識、穩定存儲和有序部署、擴展以及刪除。
StatefulSet 的設計目的是解決有狀態應用中的一些常見需求,如:
podname-0
, podname-1
),而 Deployment 中的 Pod 是無狀態的,每個 Pod 的名稱和 IP 可能會變化。以下是一個簡單的 StatefulSet 組態檔範例:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: k8s.gcr.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
serviceName: "nginx"
:指定 StatefulSet 所屬的 Headless Service,用於管理 Pod 的 DNS 名稱和連接。replicas: 3
:定義 3 個 Pod 副本,這些 Pod 是有狀態的,且每個 Pod 都會獲得一個唯一的名稱和持久存儲。volumeClaimTemplates
:每個 Pod 都會創建一個名為 www
的 PersistentVolumeClaim,並請求 1Gi 的存儲空間。/usr/share/nginx/html
,確保每個副本的數據是隔離的。接下來,我們將使用以下組態檔案來建立一個 StatefulSet 及其所依賴的 Service。該配置將建立一個無頭服務(Headless Service)nginx
,以便發布 StatefulSet web
中各個 Pod 的 IP 地址。
組態檔案: sts.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
這個組態檔案包含:
nginx
的 headless serviceweb
的 statefulsett1
,使用 --watch
flag 監控 Pod 的變化kubectl get pods --watch -l app=nginx
kubectl apply -f sts.yaml
t1
終端,觀察 Pod 的變化NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 4s
web-0 0/1 ContainerCreating 0 4s
web-0 1/1 Running 0 14s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 5s
web-1 0/1 ContainerCreating 0 5s
web-1 1/1 Running 0 14s
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 4s
web-2 0/1 ContainerCreating 0 4s
web-2 1/1 Running 0 5s
可以看到,3 個 Pod 是依序建立的,等到前一個 Pod 狀態變成 Running 後,才會開始建立下一個 Pod,直到全部 Pod 完成。
nginx
StatefulSet 的 Podkubectl get pods -l app=nginx
---
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 5m17s
web-1 1/1 Running 0 5m3s
web-2 1/1 Running 0 4m49s
for i in 0 1 2; do kubectl exec "web-$i" -- sh -c 'hostname'; done
---
web-0
web-1
web-2
可以看到,StatefulSet 建立的 Pod 使用的是累進的數字當作 hostname,這也讓 Pod 變得更好預測。
kubectl run -it --image busybox:1.36 dns-test --restart=Never --rm
之前的章節我們有提到,Headless Service 綁定 StatefulSet 的 Pod label,每個符合 selector
的 Pod 都會獲得一個 DNS 名稱。格式為 <pod-name>.<service-name>.<namespace>.svc.cluster.local
。
nslookup web-0.nginx
---
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.1.4 web-0.nginx.default.svc.cluster.local
----------
nslookup web-1.nginx
---
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.2.4 web-1.nginx.default.svc.cluster.local
----------
nslookup web-2.nginx
---
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-2.nginx
Address 1: 10.244.2.6 web-2.nginx.default.svc.cluster.local
可以看到,我們的確可以透過 DNS 名稱分別訪問每個 Pod。
接下來,我們來驗證 IP 變動的影響。
kubectl delete pod -l app=nginx
---
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted
這樣會讓 Pod 重新建立,它們會被叢集分配新的 IP。
kubectl run -it --image busybox:1.36 dns-test --restart=Never --rm
nslookup web-0.nginx
---
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.1.5 web-0.nginx.default.svc.cluster.local
----------
nslookup web-1.nginx
---
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.2.8 web-1.nginx.default.svc.cluster.local
----------
nslookup web-2.nginx
---
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-2.nginx
Address 1: 10.244.2.9 web-2.nginx.default.svc.cluster.local
可以看到 pod 對應的 IP 已經改變了,但我們依然可以使用相同的 DNS 名稱訪問對應的 Pod。
這就是為什麼不要在其他應用中使用 StatefulSet 中特定 Pod 的 IP 地址進行連接,因為 Pod 一旦重啟 IP 就會改變。
StatefulSet 控製器建立了三個 PersistentVolumeClaims, 繫結到三個 PersistentVolumes。我們來驗證一下。
kubectl get pvc -l app=nginx
---
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
www-web-0 Bound pvc-1452ead9-51d4-4eb6-ac71-62a27b87572c 1Gi RWO standard <unset> 67m
www-web-1 Bound pvc-ee106200-53cd-40cc-8cfb-b489eff8a259 1Gi RWO standard <unset> 67m
www-web-2 Bound pvc-9d9cd83c-d4e9-48ef-8352-ebf5d3317ee1 1Gi RWO standard <unset> 66m
NginX Web 伺服器默認會載入位於 /usr/share/nginx/html/index.html
的 index 檔案。 StatefulSet spec
中的 volumeMounts
欄位保證了 /usr/share/nginx/html
資料夾由一個 PersistentVolume 卷支援。
index.html
檔案並for i in 0 1 2; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done
for i in 0 1 2; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
---
web-0
web-1
web-2
kubectl delete pod -l app=nginx
---
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted
for i in 0 1 2; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
---
web-0
web-1
web-2
可以看到,即使 Pod 被重新建立,StatefulSet 依然會自動為新的 Pod 繫結對應的 PV。
我們可以將 StatefulSet 視為 ReplicaSet 的一個高級變體,因此他也有 ReplicaSet 的擴容/縮容功能。
t1
終端,重新使用 --watch
flag 監控 Pod 的變化kubectl get pods --watch -l app=nginx
web
擴展到 5kubectl scale sts web --replicas=5
# 或者使用指令:
kubectl patch sts web -p '{"spec":{"replicas":5}}'
web
縮回 2kubectl scale sts web --replicas=2
# 或者使用指令:
kubectl patch sts web -p '{"spec":{"replicas":2}}'
t1
終端,觀察 Pod 的變動情況NAME READY STATUS RESTARTS AGE
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-3 0/1 ContainerCreating 0 0
web-3 1/1 Running 0 1s
web-4 0/1 Pending 0 0s
web-4 0/1 Pending 0 0s
web-4 0/1 ContainerCreating 0 0s
web-4 1/1 Running 0 1s
web-4 1/1 Terminating 0 28s
web-4 0/1 Terminating 0 28s
web-4 0/1 Terminating 0 28s
web-4 0/1 Terminating 0 28s
web-4 0/1 Terminating 0 28s
web-3 1/1 Terminating 0 29s
web-3 0/1 Terminating 0 30s
web-3 0/1 Terminating 0 30s
web-3 0/1 Terminating 0 30s
web-3 0/1 Terminating 0 30s
web-2 1/1 Terminating 0 44s
web-2 0/1 Terminating 0 44s
web-2 0/1 Terminating 0 45s
web-2 0/1 Terminating 0 45s
web-2 0/1 Terminating 0 45s
由上面可以觀察到:
另外,如果你重新獲取 StatefulSet 的 PersistentVolumeClaims,會發現擴展到 5 個副本時,掛載到 StatefulSet Pod 的 PersistentVolume 不會被刪除。這是為了讓使用者有機會就回 Volume 中的資料。
kubectl get pvc -l app=nginx
---
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
www-web-0 Bound pvc-1452ead9-51d4-4eb6-ac71-62a27b87572c 1Gi RWO standard <unset> 171m
www-web-1 Bound pvc-ee106200-53cd-40cc-8cfb-b489eff8a259 1Gi RWO standard <unset> 170m
www-web-2 Bound pvc-9d9cd83c-d4e9-48ef-8352-ebf5d3317ee1 1Gi RWO standard <unset> 170m
www-web-3 Bound pvc-192ba651-004b-4ef3-af6c-e95adea1a288 1Gi RWO standard <unset> 53m
www-web-4 Bound pvc-656565e5-d3a5-4114-804e-ca14dd0c31ac 1Gi RWO standard <unset> 53m